/*  
 * Copyright 2007 MBARI 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.mbari.timeline.piccolo;

import java.awt.BasicStroke;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;

import org.mbari.timeline.DateUtil;

import com.domainlanguage.base.Ratio;
import com.domainlanguage.base.Rounding;
import com.domainlanguage.time.Duration;
import com.domainlanguage.time.TimeInterval;
import com.domainlanguage.time.TimePoint;

import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolox.nodes.PLine;

/** 
 * @author Andrew Chase
 */
@SuppressWarnings("serial")
public class Timeline extends PNode {

    private TimeInterval timeRange;
    public static double DAY_WIDTH_DEFAULT = 150.0;
    private static long millisecondsInADay = 1000 * 60 * 60 * 24;
    private double height = 150;

    /**
     * The timeline currently assumes the range begins and ends at midnight. If this is not
     * true then the rendering of the date boundaries will be incorrect.
     * 
     * @param range
     */
    public Timeline(TimeInterval range) {
        if(!validateTimeRange(range)){
            System.err.println("Invalid time range, start and end times must be midnight");
            throw new IllegalArgumentException("Invalid time range");
        }
        this.timeRange = range;
        long timespan = DateUtil.lengthInMillis(getTimeRange());
        double width = timespan / millisecondsInADay * DAY_WIDTH_DEFAULT;
        setBounds(0, 0, width, height);
        setPaint(Color.gray);
        addDays();
        addTide();
    }
    
    private boolean validateTimeRange(TimeInterval range){
        TimePoint midnightStart = range.start().backToMidnight(DateUtil.GMT_TIMEZONE);
        if(!midnightStart.equals(range.start())){
            return false;
        }
        TimePoint midnightEnd = range.end().backToMidnight(DateUtil.GMT_TIMEZONE);
        if(!midnightEnd.equals(range.end())){
            return false;
        }
        return true;
    }

    //in local coordinates
    public TimePoint xCoordinateToDate(double x) {
        //	    Ratio positionRatioHolder = Ratio.of(BigDecimal.valueOf(x), BigDecimal.valueOf(getWidth()));
        //	    Ratio xToPositionRatio = positionRatioHolder.times(timeRange.length().dividedBy(Duration.seconds(1)));
        long timeSpan = DateUtil.lengthInMillis(timeRange);
        double percentOfTotal = x / this.getWidth();
        long timeFromStart = (long) (timeSpan * percentOfTotal);
        long timeOfX = timeRange.start().asJavaCalendar(DateUtil.GMT_TIMEZONE).getTimeInMillis()+ timeFromStart;
        return TimePoint.from(timeOfX);
    }

    public double dateToXCoordinate(TimePoint date) {
        Duration totalTimeRange = getTimeRange().length();
        Duration fractionalTimeRange = getTimeRange().start().until(date).length();
        Ratio timeRatio = fractionalTimeRange.dividedBy(totalTimeRange);
        BigDecimal xWidth = BigDecimal.valueOf(this.getWidth());
        double xCoordinate = timeRatio.times(xWidth). //
            decimalValue(8, Rounding.HALF_UP).doubleValue();
        return xCoordinate;
    }

    public TimeInterval getTimeRange() {
        return timeRange;
    }

    public void addEventNode(TwoPartEventNode event) {
        this.addChild(event);
        double xPosition = dateToXCoordinate(event.getEvent().getTime());
        System.out.println("xPosition: " + xPosition);
        event.setOffset(xPosition, 0);
        System.out.println("event x: " + event.getBounds());
    }

    private void addTide(){
        BufferedReader tideFile = new BufferedReader(new InputStreamReader(TimelineCanvas.class
            .getResourceAsStream("/tides/Moss_Landing_tides_2007.tsv")));
        TimeSeries tide = TimeSeries.makeTideNode(tideFile, "\\t");
        PPath tideNode = tide.getTimeSeriesNode(getTimeRange().start(), getTimeRange().end(), this);
        tideNode.translate(0, 90);
        addChild(tideNode);
    }

    public void addDays() {
        PNode[] days = createDays(height, DAY_WIDTH_DEFAULT, 0, 0);
        for (PNode day : days) {
            addChild(day);
        }
    }

    public PNode[] createDays(double height, double width, int xOffset, int yOffset) {
        long timeSpan = DateUtil.lengthInMillis(timeRange);
        int numDays = (int) Math.ceil(timeSpan / millisecondsInADay);
        PNode[] days = new PNode[numDays];
        int x = xOffset;
        int y = yOffset;
        TimePoint currentDate = timeRange.start();
        for (int i = 0; i < numDays; i++) {
            PLine border = createDateBox(height, width, x, y);
            String dateString = (DateUtil.extractMonth(currentDate)) + "/"
                + DateUtil.extractDay(currentDate);
            currentDate = currentDate.nextDay();
            PNode date = makeDateNode(dateString, 20, 15);
            days[i] = new PNode();
            days[i].addChild(date);
            date.translate(x, y);
            days[i].addChild(border);
            days[i].setBounds(x, y, x + width, y + height);
            x += width;
        }
        return days;
    }

    private PLine createDateBox(double height, double width, int x, int y) {
        PLine border = new PLine();
        border.setStroke(new BasicStroke(2.0f));
        border.setStrokePaint(Color.black);
        border.setBounds(x, y, width, height);
        border.setPaint(Color.gray);
        border.addPoint(0, x, y);
        border.addPoint(0, x + width, y);
        border.addPoint(0, x + width, y + height);
        border.addPoint(0, x, y + height);
        border.addPoint(0, x, y);
        return border;
    }

    private PNode makeDateNode(String dateString, int width, int height) {
        PNode container = new PNode();
        container.setBounds(0, 0, width, height);
        container.setPaint(Color.gray);
        PText date = new PText(dateString);
        date.setTextPaint(Color.black);
        date.setBounds(2, 2, width - 4, height - 4);
        container.addChild(date);
        double borderWidth = date.getWidth();
        double borderHeight = date.getHeight();
        double xOffset = 2;
        double yOffset = 2;
        PLine border = new PLine();
        border.setStroke(new BasicStroke(2.0f));
        border.setStrokePaint(Color.black);
        border.addPoint(0, borderWidth + xOffset, yOffset);
        border.addPoint(1, borderWidth + xOffset, borderHeight + yOffset);
        border.addPoint(2, xOffset, borderHeight + yOffset);
        container.addChild(border);
        border.translate(2, 2);
        return container;
    }

}
